package org.box.demos.multithreading.wgetmt.core.storage;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;

import org.box.demos.multithreading.wgetmt.Configuration;
import org.box.demos.multithreading.wgetmt.core.ContentParser;
import org.box.demos.multithreading.wgetmt.core.ContentType;
import org.box.demos.multithreading.wgetmt.core.Converter;
import org.box.demos.multithreading.wgetmt.core.Storage;
import org.box.demos.multithreading.wgetmt.core.URL;
import org.box.demos.multithreading.wgetmt.j16mt.ProgressMonitor;

/**
 * Implements {@link Storage} for storing files in the File System.
 * 
 * @author <a href="mailto:alexander.box@gmail.com">Alexander Korobka</a>
 * 
 */
public class FileStorage implements Storage {
	
	// uniqueUrlsSet is a bottleneck of the application. It collects all urls
	// and check url's existence any time then obtained new urls
	private final Set<URL> uniqueUrlsSet = new LinkedHashSet<URL>(2000);
	
	private static final int MAX_FILE_DIR_LENGTH = 100;

	private String outDir;
	
	private final ContentParser parser = new ContentParser();
	private final Converter converter = new FileConverter();
	
	@Override
	public void connect(Configuration cfg) throws IOException {
		this.outDir = cfg.getOutDir();
	}
	
	@Override
	public void disconnect() throws IOException {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.box.demos.multithreading.wgetmt.core.Storage#write(org.box.demos.
	 * multithreading.wgetmt.core.URL, byte[],
	 * org.box.demos.multithreading.wgetmt.core.ContentParser.ContentType)
	 */
	@Override
	public void write(URL url, byte[] content)
			throws IOException {

		boolean isHtml = url.getContentType() == ContentType.TEXT_HTML;
		String filePath = convertToFilePath(url, isHtml, true);

		String dir = "";
		int ind = filePath.lastIndexOf(File.separator);
		if (ind > -1) {
			dir = filePath.substring(0, ind + 1);
			filePath = filePath.substring(ind + 1);
		}

		new File(outDir + dir).mkdirs();

		File file = new File(outDir + dir + File.separatorChar + filePath);
		if (file.exists()) {
			file.delete();
		}

		OutputStream os = new FileOutputStream(file);
		try {
			os.write(content);
			os.flush();
		} finally {
			os.close();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.box.demos.multithreading.wgetmt.core.Storage#read(org.box.demos.
	 * multithreading.wgetmt.core.URL)
	 */
	@Override
	public String read(URL url) throws IOException {
		String filePath = convertToFilePath(url, true, true);
		FileReader reader = new FileReader(outDir + filePath);
		StringBuilder sb = new StringBuilder();

		int len;
		char[] buff = new char[1024];
		while ((len = reader.read(buff)) > -1) {
			sb.append(buff, 0, len);
		}
		return sb.toString();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.box.demos.multithreading.wgetmt.core.Storage#convert(org.box.demos
	 * .multithreading.wgetmt.core.URL,
	 * org.box.demos.multithreading.wgetmt.core.ContentParser.ContentType)
	 */
	@Override
	public boolean convert(URL url) throws IOException {
		boolean isHtml = url.getContentType() == ContentType.TEXT_HTML;
		if (isHtml) {
			String content = read(url);

			content = parser.convertUrls(url, content, converter, this);
			write(url, content.getBytes());

		}
		return isHtml;
	}

	@Override
	public Collection<URL> loadIncomplete() {
		// FileStorage doesn't support download resume.
		return new LinkedHashSet<URL>();
	}

	@Override
	public Set<URL> addUrls(Set<URL> newUrls) {
		Set<URL> set = new LinkedHashSet<URL>();
		for (URL url : newUrls) {
			if (!uniqueUrlsSet.contains(url)) {
				set.add(url);
				uniqueUrlsSet.add(url);
			}
		}
		return set;
	}

	@Override
	public boolean containsUrl(URL url) {
		return uniqueUrlsSet.contains(url);
	}

	@Override
	public void convertLinks(ProgressMonitor pm) throws IOException {
		
		for (URL url : uniqueUrlsSet) {
			if (url.getContentType() == ContentType.TEXT_HTML) {
				if (convert(url)) {
					pm.onContentConverted(url);
				}
			}
		}
	}



	/**
	 * The main method that converts the given url to the file name to store
	 * 
	 * @param url - the given utl to converting
	 * @param isHtml - is the url's content type HTML?
	 * @param exludeInnerPath - the path inside of page. It starts after '#' char.  
	 * 
	 * @return a file name to store
	 * 
	 */
	public static String convertToFilePath(URL url, boolean isHtml, boolean exludeInnerPath) {

		String urlStr = url.getPath();
		if (urlStr.endsWith("/")) {
			urlStr += "index.html";
		}

		String[] parts = urlStr.split("/");
		String fileName = parts[parts.length - 1];
		if (exludeInnerPath && fileName.contains("#")) {
			fileName = fileName.substring(0, fileName.indexOf('#'));
		}
		
		if (isHtml && (!fileName.toLowerCase().endsWith(".html") 
						&& !fileName.toLowerCase().endsWith(".htm"))) {
			fileName += ".html";
		}

		if (!url.getQuery().isEmpty()) {
			int ind = fileName.lastIndexOf(".");
			fileName = (ind != -1) ? fileName.substring(0, ind)
					+ String.format("[%s]", escape(url.getQuery()))
					+ fileName.substring(ind) : fileName
					+ String.format("[%s]", escape(url.getQuery()));
		}

		String filePath = "";
		for (int i = 0; i < parts.length - 1; i++) {
			filePath += File.separatorChar + escape(parts[i]);
		}

		return filePath + File.separatorChar + fileName;
	}

	/**
	 * 
	 * 
	 * @param name
	 * @return
	 */
	private static String escape(String name) {
		return name
				.replaceAll("[\\/ \\? \\< \\> \\\\ \\: \\* \\|]", "_")
				.substring(
						0,
						name.length() > MAX_FILE_DIR_LENGTH ? MAX_FILE_DIR_LENGTH
								: name.length());
	}

	/**
	 * Implements {@link Converter} API to convert urls to the file system's
	 * path format
	 * 
	 * @author <a href="mailto:alexander.box@gmail.com">Alexander Korobka</a>
	 * 
	 */
	public final class FileConverter implements Converter {

		/**
		 * The hidden constructor
		 */
		private FileConverter() {
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.box.demos.multithreading.wgetmt.core.Converter#convert(org.box
		 * .demos.multithreading.wgetmt.core.URL,
		 * org.box.demos.multithreading.wgetmt.core.URL,
		 * org.box.demos.multithreading.wgetmt.core.ContentParser.ContentType)
		 */
		@Override
		public String convert(URL url, URL parentUrl) {
			if (url.getContentType() == ContentType.UNKNOWN) {
				return url.toString();
			}
			// FIXME:
			boolean isHtml = url.getContentType() == ContentType.TEXT_HTML;
			int levels = parentUrl.getDeepLevel();
			String filePath = convertToFilePath(url, isHtml, false);

			for (int i = 0; i < levels; i++) {
				filePath = File.separator + ".." + filePath;
			}

			return "." + filePath.replace(File.separator, "/");
		}

	}
}
